public class Document : Gtk.VBox
{
	public enum DocumentState { NO_MODIFIED, MODIFIED, READ_ONLY }

	public static Gtk.SourceLanguageManager source_lang_manager = new Gtk.SourceLanguageManager ();
	
	protected Gtk.SourceLanguage?   source_lang = null; 
	protected Gtk.SourceBuffer	source_buff;		
	protected Gtk.SourceView	source_view;

	protected GoToLinePanel		goto_line_panel;
	protected SearchPanel 		search_panel;
	protected ReplacePanel		replace_panel;
  
	private	Gtk.ScrolledWindow	scrolled_wnd = new Gtk.ScrolledWindow (null, null);	

	private string file_path = null;	
	private DocumentState doc_state;
	
	private bool doc_panel_mutex = false;
	
	public DocumentState state
	{
		get { return this.doc_state; }
	}
		
	construct
	{
		this.source_buff = new Gtk.SourceBuffer (new Gtk.TextTagTable ());
		this.source_buff.modified_changed += this.on_modified_changed;
		this.source_view = new Gtk.SourceView.with_buffer (this.source_buff);
		
		this.scrolled_wnd.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
		this.scrolled_wnd.add (source_view);
	
		this.pack_start_defaults (scrolled_wnd);
		this.pack_start (new Gtk.HSeparator (), false, false, 0);
		
		this.goto_line_panel = new GoToLinePanel (ref this.source_buff, ref this.source_view);
		this.goto_line_panel.show_panel  += this.on_show_doc_panel;
		this.goto_line_panel.close_panel += this.on_hide_doc_panel; 		
		this.pack_start (this.goto_line_panel, false, false, 0);
		
		this.search_panel = new SearchPanel (ref this.source_buff, ref this.source_view);
		this.search_panel.show_panel  += this.on_show_doc_panel;
		this.search_panel.close_panel += this.on_hide_doc_panel; 
		this.pack_start (this.search_panel, false, false, 0);
		
		this.replace_panel = new ReplacePanel (ref this.source_buff, ref this.source_view);
		this.replace_panel.show_panel  += this.on_show_doc_panel;
		this.replace_panel.close_panel += this.on_hide_doc_panel; 
		this.pack_start (this.replace_panel, false, false, 0);
		
		this.source_buff.changed += this.search_sensitive_update;
		this.source_view.focus_in_event += this.on_focus_in_event;
	}

	//Ustawia source_lang na podstawie mime type pliku file_path	
	private void auto_set_language ()
	{
		try
		{
			string doc_mime_type = Utils.get_mime_type (this.file_path); 			
			
			Gtk.SourceLanguage lang;
			
			string** lang_ids = (string**) source_lang_manager.get_language_ids ();
			if (lang_ids != null)
			{
				for (int i = 0; *(lang_ids + i) != null; ++i)
				{
					string lang_id = *(lang_ids + i);
					lang = source_lang_manager.get_language (
						lang_id
					);
		
					string** list = lang.get_mime_types ();
					if (list != null) 
					{
						for (int j = 0; *(list + j) != null; ++j)
						{
							if (doc_mime_type == *(list + j))
							{								
								this.source_lang = this.source_lang_manager.get_language (
									lang_id
								);
								this.source_buff.set_language (this.source_lang);
								return ;
							}
						}
					}												
				}	
			}
		}
		catch (Error er)
		{
			stdout.printf ("%s", er.message);
		}
	}

	public void set_contents_from_file (string file_path)
	{
		this.file_path = file_path;
	
		string content = null;
		int   length = 0;
		try
		{
			if (GLib.FileUtils.get_contents (this.file_path, out content, out length))
			{
				this.source_buff.set_text (content, length);
				this.auto_set_language ();
				this.source_buff.set_modified (false);
				this.search_sensitive_update ();
				Gtk.RecentManager.get_default().add_item (GLib.Filename.to_uri (this.file_path));				
			}				
		}
		catch (GLib.FileError er)
		{
			stdout.printf ("Error %s\n", er.message);
		}
	}
	
	public string? get_content ()
	{
		Gtk.TextIter start, end;
		this.source_buff.get_start_iter (out start);
		this.source_buff.get_end_iter (out end);
		
		return this.source_buff.get_text (
		        start, end, true
                       );	
	}
	
	protected void on_modified_changed ()
	{
		doc_state = (this.source_buff.get_modified ()) ? DocumentState.MODIFIED : DocumentState.NO_MODIFIED;	
		((DocumentHeader) DocumentManager.get_instance().get_tab_label (this)).set_state (state);
	}
	
	public void search_sensitive_update ()
	{
		if (this.source_buff.get_char_count () == 0)
		{
			UIManager.get_instance().get_action("search_find").set_sensitive (false);
			UIManager.get_instance().get_action("search_replace").set_sensitive (false);
			UIManager.get_instance().get_action("search_goto_line").set_sensitive (false);
		}
		else if (this.source_buff.get_char_count () >= 1)
		{
			UIManager.get_instance().get_action("search_find").set_sensitive (true);
			UIManager.get_instance().get_action("search_replace").set_sensitive (true);		
			UIManager.get_instance().get_action("search_goto_line").set_sensitive (true);
		}
	}
	
	public void insert_text_at_cursor (string text)
	{
		this.source_buff.insert_at_cursor (text, (int) text.length); 
	}
		
	public Document (string? file_path = null)
	{
		this.file_path = file_path;
		if (this.file_path != null)
		{
			this.set_contents_from_file (this.file_path);
		}
		this.show_all ();
	}
	
	public bool save ()
	{
		if (this.file_path != null)
		{
			try 
			{
				Gtk.TextIter start, end;
				this.source_buff.get_start_iter (out start);
				this.source_buff.get_end_iter (out end);
				
				GLib.FileUtils.set_contents (
					this.file_path, 
					this.source_buff.get_text (
						start, end, true
					)					
				);
				if (source_lang == null)
				{
					this.auto_set_language ();	
				}
				source_buff.set_modified (false);								
			}
			catch (GLib.FileError er)
			{
				return false;
			}
		}
		else 
		{
			this.save_as ();
		}
		return true;
	}
	
	public bool save_as ()
	{
		var dlg = new Gtk.FileChooserDialog (	
			"Zapisz jako", null,
			Gtk.FileChooserAction.SAVE,
			Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
			Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT
		);
		if (dlg.run () == Gtk.ResponseType.ACCEPT)
		{
			this.file_path = dlg.get_filename ();
			if (this.save ())
			{
				((DocumentHeader) DocumentManager.get_instance().get_tab_label (this)).text = GLib.Path.get_basename (file_path);
				Gtk.RecentManager.get_default().add_item (dlg.get_uri ());				
			}
			else
			{
				this.file_path = null;	
			}			
		}
		else
		{
			dlg.destroy ();
			return false;
		}
		dlg.destroy ();
			
		return true;
	}
	
	public bool close ()
	{
		if (source_buff.get_modified ())
		{
			var msg = new Gtk.MessageDialog.with_markup (
				null, 
				Gtk.DialogFlags.MODAL,
				Gtk.MessageType.QUESTION,
				Gtk.ButtonsType.NONE, 
				"Plik \"<b>" + ((this.file_path != null) ? this.file_path : ((DocumentHeader) DocumentManager.get_instance().get_tab_label (this)).text) + "\"</b> został zmodyfikowany !" 
			);
			msg.add_button (Gtk.STOCK_YES, Gtk.ResponseType.YES);
			msg.add_button (Gtk.STOCK_NO, Gtk.ResponseType.NO);			
			msg.add_button (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL);
			msg.format_secondary_text ("Czy chcesz zapisać zmiany ?");
			
			int response = msg.run ();
			msg.destroy ();
			if (response == Gtk.ResponseType.YES)
			{
				this.save ();				
			}
			else if (response == Gtk.ResponseType.CANCEL) 
			{
				return false;
			}					
		}
		
		return true;
	}
	
	public inline void undo ()
	{
		if (this.source_buff.can_undo)
		{
			this.source_buff.undo ();			
		}
	}

	public inline void redo ()
	{
		if (this.source_buff.can_redo)
		{
			this.source_buff.redo ();	
		}
	}
	
	public inline void cut ()
	{
		Gtk.Clipboard clipboard = null;
		
		clipboard = this.source_view.get_clipboard (Gdk.SELECTION_CLIPBOARD);
		this.source_buff.cut_clipboard (clipboard, true);		
	}
	
	public inline void copy ()
	{
		Gtk.Clipboard clipboard = null;
		
		clipboard = this.source_view.get_clipboard (Gdk.SELECTION_CLIPBOARD);
		this.source_buff.copy_clipboard (clipboard);
	}
	
	public inline void paste ()
	{
		Gtk.Clipboard clipboard = null;
		
		clipboard = this.source_view.get_clipboard (Gdk.SELECTION_CLIPBOARD);
		this.source_buff.paste_clipboard (clipboard, null, true);	
	}
	
	public inline void delete_selection ()
	{
		this.source_buff.delete_selection (false, false);
	}
	
	public inline void select_all ()
	{
		Gtk.TextIter start, end;
		this.source_buff.get_iter_at_offset(out start, 0);
		this.source_buff.get_end_iter (out end);
		
		this.source_buff.select_range (start, end); 
	}
	
	public inline void grab_focus ()
	{
		this.source_view.grab_focus ();
	}
	
	public void show_search_panel ()
	{	
		if (!this.search_panel.visible)
		{
			if (this.replace_panel.visible)
			{
				this.search_panel.match_case = this.replace_panel.match_case;
				this.search_panel.match_words = this.replace_panel.match_words;
				this.search_panel.match_regex = this.replace_panel.match_regex;
				this.search_panel.search_pharse  = this.replace_panel.search_pharse ;
			
				this.replace_panel.close ();
			}
			this.search_panel.show ();
		}
		else
		{
			this.search_panel.close ();
		}
	}

	public void show_replace_panel ()
	{
		if (!this.replace_panel.visible)
		{
			if (this.search_panel.visible)
			{
				this.replace_panel.match_case = this.search_panel.match_case;	
				this.replace_panel.match_words = this.search_panel.match_words;
				this.replace_panel.match_regex = this.search_panel.match_regex;			
				this.replace_panel.search_pharse  = this.search_panel.search_pharse ;
				
				this.search_panel.close ();			
			}
			
			this.replace_panel.show ();
		}
		else
		{
			this.replace_panel.close ();
		}
	}
	
	public void goto_next_found_match ()
	{
		this.search_panel.goto_next_match ();
	}

	public void goto_prev_found_match ()
	{
		this.search_panel.goto_prev_match ();
	}
	
	public void show_goto_line_panel ()
	{
		if (!this.goto_line_panel.visible)
		{			
			this.goto_line_panel.show ();			
		}
		else
		{
			this.goto_line_panel.close ();
		}
	}
	
	protected virtual bool on_focus_in_event (Gtk.SourceView source_view, Gdk.EventFocus event) 
	{
		this.search_sensitive_update ();
		return false;	
	}
	
	protected virtual bool on_show_doc_panel ()
	{
		if (!this.doc_panel_mutex && this.source_buff.get_char_count () != 0)
		{			
			this.doc_panel_mutex = true;
			
			return false;
		}
		return true;		
	}

	protected virtual bool on_hide_doc_panel ()
	{
		this.doc_panel_mutex = false;
		return false;
	}
}
